package demo.customer.security;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.util.matcher.RequestMatcher;

import demo.customer.security.error.DemoAccessDeniedHandler;
import demo.customer.security.error.DemoAuthenticationEntryPoint;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
    
	@Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
		
		resources.authenticationEntryPoint(new DemoAuthenticationEntryPoint())
				.accessDeniedHandler(new DemoAccessDeniedHandler());
		
	}
	
	@Override
	public void configure(HttpSecurity http) throws Exception {
		
		log.info("**********ResourceServerConfiguration:configure(HttpSecurity http)**********");
		
		RequestMatcher rm = new RequestMatcher() {
			@Override
			public boolean matches(HttpServletRequest request) {
				boolean isMatched = false;
				
				String authorization = request.getHeader("Authorization");
				if(authorization != null && authorization.startsWith(OAuth2AccessToken.BEARER_TYPE)) {
					isMatched = true;
				}
				//check if access token exists
				String accessToken = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
				if(accessToken != null) {
					isMatched = true;
				}
				
				String requestURI = request.getRequestURI();
				log.debug("***** RequestMatcher: isMatched:{}, authorization:{}, accessToken:{}, requestURI:{}",isMatched, authorization,accessToken, requestURI);
				
				return isMatched;
			}			
		};
		
//		RequestMatcher rmActuator = new RequestMatcher() {
//			@Override
//			public boolean matches(HttpServletRequest request) {
//				boolean isMatched = false;
//								
//				String requestURI = request.getRequestURI();
//				if(requestURI.startsWith("/actuator")) {
//					isMatched = true;
//				}
//				log.debug("***** actuator RequestMatcher: isMatched:{},requestURI:{}",isMatched, requestURI);
//				
//				return isMatched;
//			}			
//		};
		
		// @formatter:off
		http
			.authorizeRequests().requestMatchers(rm).authenticated().and()
			//TODO:fix me to suppress anonymous access
			.authorizeRequests().antMatchers("/actuator/*").anonymous();
		// @formatter:on		
	}
	
	@Configuration
	@Conditional(DemoJwtTokenCondition.class)
	protected static class JwtTokenConfiguration {
		
		public JwtTokenConfiguration() {
			log.info("**********JwtTokenConfiguration**********");
		}
		
		@Bean
		@ConditionalOnMissingBean(ResourceServerTokenServices.class)
		public DefaultTokenServices jwtTokenServices(TokenStore jwtTokenStore) {
			log.info("**********jwtTokenServices**********");
			DefaultTokenServices services = new DefaultTokenServices();
			services.setTokenStore(jwtTokenStore);
			return services;
		}
		

		@Bean
		@ConditionalOnMissingBean(TokenStore.class)
		public TokenStore jwtTokenStore() {
			log.info("**********jwtTokenStore**********");
			return new JwtTokenStore(jwtTokenEnhancer1());
		}
		

		@Bean
		@Primary
		public JwtAccessTokenConverter jwtTokenEnhancer1() {
			
			log.info("********************@Primary JwtAccessTokenConverter ******************");
			
			Resource resource = new ClassPathResource("public.cert");
			
			JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
					
			try {			
				String publicKey = new String(IOUtils.toByteArray(resource.getInputStream()));
				converter.setVerifierKey(publicKey);
			}
			catch(IOException e) {
				throw new RuntimeException("Failed to read public.cert", e);
			}
			
			return converter;
		}
		
	}
	
	private static class DemoJwtTokenCondition extends SpringBootCondition {

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {

			ConditionMessage.Builder message = ConditionMessage.forCondition("tokenStore Condition");
			Environment environment = context.getEnvironment();
			String tokenStore = environment.getProperty("demo.tokenStore");
			if ("JWT".equals(tokenStore)) {
				return ConditionOutcome.match(message.foundExactly("JWT tokenStore"));
			}
			
			return ConditionOutcome.noMatch(message.didNotFind("JWT tokenStore").atAll());
		}

	}
}
